package main;

import java.awt.Point;
import java.util.Vector;

/**
 * A grid of tiles, each tile can either be empty, white or black.
 * @author ES
 */
public class Grid {
	
	private char[][] grid;
	
	public enum Heuristic {TILES, EDGES, CORNERS}
	
	Heuristic heuristic = Heuristic.TILES;
	
	/**
	 * Creates a grid from scratch with four chips placed in the center.
	 */
	public Grid() {
		createGrid();
	}
	
	/**
	 * Creates a copy of the copyGrid.
	 * @param copyGrid : The grid to be copied
	 */
	public Grid(Grid copyGrid){
		copyGrid(copyGrid);
	}
	
	
	/**
	 * Copies the specified grid into this grid
	 * @param copyGrid : the grid to be copied
	 */
	private void copyGrid(Grid copyGrid) {
		grid = new char[8][8];
		
		for(int i = 0; i < grid.length; ++i)
		{
			for(int j = 0; j < grid[i].length; ++j)
				grid[i][j] = copyGrid.getGrid()[i][j];
		}
		heuristic = copyGrid.getHeuristic();
	}

	/**
	 * Composes a new grid with two white and two black pieces in the center
	 *  0 = Empty Tile
	 *  W = White Tile
	 *  B = Black Tile
	 * 
	 */
	public void createGrid() {
		grid = new char[8][8];
		
		for(int i = 0; i < grid.length; ++i)
		{
			for(int j = 0; j < grid[i].length; ++j)
				grid[i][j] = '0';
		}
		
		grid[3][3] = 'W'; //White
		grid[3][4] = 'B'; //Black
		grid[4][3] = 'B'; //Black
		grid[4][4] = 'W'; //white
	}
	
	
	//GETTERS AND SETTERS
	/**
	 * Sets the grid.
	 */
	public void setGrid(char[][] grid) {
		this.grid = grid;
	}

	/**
	 * @return game grid.
	 */
	public char[][] getGrid() {
		return grid;
	}
	
	/**
	 * Return the character stored at this position.
	 * @param row : row position
	 * @param col : column position
	 * @return either 'B' 'W' or '0'
	 */
	public char getElementAt(int row, int col)
	{
		//Check for valid bounds
		if(checkBounds(row, col))
			return grid[row][col];
		
		return '0';
	}
	
	/**
	 * Returns a list of {@link Move}s that are valid for the current color.
	 * @param color : The color of the current player's pieces
	 * @return A list of moves that are legal
	 */
	public Vector<Move> GetLegalMoves(char color){
		Vector<Move> Moves = new Vector<Move>();
		
		for(int row = 0; row < grid.length; ++row)
		{
			for(int col = 0; col < grid[row].length; ++col)
			{
				if(grid[row][col] == '0') //we can place a color!
				{
					int score = checkElementAt(row, col, color);
					if(score > 0)
						Moves.add(new Move(row, col, score));
				}
			}
		}
		return Moves;
	}
	
	/**
	 * Sets one element of the grid.
	 * the parameter value should be 'B' for black or 'W' for white or '0' for empty.
	 */
	public boolean setElementAt(int row, int col, char value)
	{
		//Check for valid bounds
		if(checkBounds(row, col)){
			if(value == 'B' || value == 'W' || value == '0') //Check for legal values
			{
				return setValue(row, col, value);
			}
		}
		return false;
	}
	
	/**
	 * Checks if a position is a valid location for a move.
	 * @param row
	 * @param col
	 * @param value : The color of the player placing this {@link Move}
	 * @return The score associated with this move
	 */
	public int checkElementAt(int row, int col, char value){
		int score = 0;
		//Check for valid bounds
		if(checkBounds(row, col)){
			if(value == 'B' || value == 'W' || value == '0') //Check for legal values
			{
				score = checkLines(row,col, value);
			}
		}
		return score;
		
	}

	/**
	 * Checks if a {@link Move} is within the bounds of the Grid
	 * @param row
	 * @param col
	 * @return is the {@link Move} within bounds
	 */
	private boolean checkBounds(int row, int col) {
		return row >= 0 && row <grid.length && col >= 0 && col < grid[row].length;
	}
	
	/**
	 * Sets the value at the specified location to a certain value
	 * @param row
	 * @param col
	 * @param value : The character value to place at the location
	 * @return
	 */
	private boolean setValue(int row, int col, char value){
		if(setLines(row, col, value)){
			grid[row][col] = value;
			return true;
		}
		return false;
	}

	/**
	 * Flips any pieces that need to be flipped
	 * Checks all the cardinal directions
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any moves placed
	 */
	private boolean setLines(int row, int col, char value) {
		
		boolean set[] = new boolean[8];
				
		set[0] = setUp(row, col, value);
		set[1] = setUpRight(row, col, value);
		set[2] = setRight(row, col, value);
		set[3] = setDownRight(row, col, value);
		set[4] = setDown(row, col, value);
		set[5] = setDownLeft(row, col, value);
		set[6] = setLeft(row, col, value);
		set[7] = setUpLeft(row, col, value);
		
		for (int i = 0; i < set.length; i++) {
			if(set[i])
				return set[i];
		}
		return false;
	}
	
	/**
	 * Flips the pieces in a certain direction until conditions are met
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @param x_dir : x direction
	 * @param y_dir : y direction
	 * @return were any tiles succesfully flipped
	 */
	private boolean setValue(int row, int col, char value, int x_dir, int y_dir){
		Vector<Point> Line = new Vector<Point>();
		boolean hitObstacle = false;
		boolean hitEnd = false;
		do{
			//check row/col
			row += x_dir;
			col += y_dir;
			
			if(checkBounds(row, col) && grid[row][col] != '0'){
				if(value == grid[row][col]){
					hitObstacle = true;
					hitEnd = true;
				}else{
					Line.add(new Point(row,col));
				}
			}else{
				hitObstacle = true;
			}
		}while(!hitObstacle);
		if(!hitEnd){
			Line.clear();
		}
		for(Point p : Line){
			grid[p.x][p.y] = value;
		}
		
		return hitEnd && hitObstacle && Line.size() != 0;
	}

	/**
	 * Flips tiles in the Up and Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setUpLeft(int row, int col, char value) {
		return setValue(row, col, value, -1, -1);
	}

	/**
	 * Flips tiles in the Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setLeft(int row, int col, char value) {
		return setValue(row, col, value, -1, 0);	
	}

	/**
	 * Flips tiles in the Down and Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setDownLeft(int row, int col, char value) {
		return setValue(row, col, value, -1, 1);	
	}

	/**
	 * Flips tiles in the Down direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setDown(int row, int col, char value) {
		return setValue(row, col, value, 0, 1);		
	}

	/**
	 * Flips tiles in the Down and Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setDownRight(int row, int col, char value) {
		return setValue(row, col, value, 1, 1);	
	}

	/**
	 * Flips tiles in the Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setRight(int row, int col, char value) {
		return setValue(row, col, value, 1, 0);		
	}

	/**
	 * Flips tiles in the Up and Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setUpRight(int row, int col, char value) {
		return setValue(row, col, value, 1, -1);	
	}

	/**
	 * Flips tiles in the Up direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return were any tiles flipped successfully
	 */
	private boolean setUp(int row, int col, char value) {
		return setValue(row, col, value, 0, -1);		
	}
	
	/**
	 * Checks if any tile will be flipped
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkLines(int row, int col, char value) {
		return checkUp(row, col, value) + checkUpRight(row, col, value) 
		+ checkRight(row, col, value)	+ checkDownRight(row, col, value) 
		+ checkDown(row, col, value)	+ checkDownLeft(row, col, value) 
		+ checkLeft(row, col, value) + checkUpLeft(row, col, value);
	}
	
	/**
	 * Gets the score associated with a tile being place at this location
	 * and tiles being flipped in the given direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @param x_dir : x direction
	 * @param y_dir : y direction
	 * @return The score of a tile being place at that location
	 */
	private int getScore(int row, int col, char value, int x_dir, int y_dir){
		int score = 0;
		switch(heuristic){
		//-- Number of tiles gained
		case TILES:
			boolean hitObstacle = false;
			boolean hitEnd = false;
			do{
				//check row/col
				row += x_dir;
				col += y_dir;

				if(checkBounds(row, col) && grid[row][col] != '0'){
					if(value == grid[row][col]){
						hitObstacle = true;
						hitEnd = true;
					}else{
						score++;
					}
				}else{
					hitObstacle = true;
				}
			}while(!hitObstacle);
			if(!hitEnd){
				score = 0;
			}
			break;
			
		//-- Proximity to edges
		case EDGES:
			int baseScore1 = grid.length;
			int edgeW, edgeE, edgeN, edgeS;
			edgeW = baseScore1 - (col+1);
			edgeE = baseScore1 - (baseScore1 - (col+1));
			edgeN = baseScore1 - (row+1);
			edgeS = baseScore1 - (baseScore1 - (row+1));
			score = Math.max(edgeW, Math.max(edgeE, Math.max(edgeN, edgeS)));
			break;
			
		//-- proximity to edges
		case CORNERS:
			int baseScore2 = grid.length * grid.length;
			int cornerNW, cornerNE, cornerSE, cornerSW;
			cornerNW = baseScore2 - (row+1)*(col+1);
			cornerNE = baseScore2 - (row+1) * (grid.length - (col+1));
			cornerSE = baseScore2 - (grid.length - (row+1)) * (grid.length - (col+1));
			cornerSW = baseScore2 - (grid.length - (row+1)) * (col+1);
			score = Math.max(cornerNE, Math.max(cornerNW, Math.max(cornerSE, cornerSW)));
			break;
		}
		
		return score;
	}
	
	/**
	 * Checks if any tile will be flipped in the Up and Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkUpLeft(int row, int col, char value) {
		return getScore(row, col, value, -1, -1);
	}

	/**
	 * Checks if any tile will be flipped in the Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkLeft(int row, int col, char value) {
		return getScore(row, col, value, -1, 0);
	}

	/**
	 * Checks if any tile will be flipped in the Down and Left direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkDownLeft(int row, int col, char value) {
		return getScore(row, col, value, -1, 1);
	}

	/**
	 * Checks if any tile will be flipped in the Down direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkDown(int row, int col, char value) {
		return getScore(row, col, value, 0, 1);
	}

	/**
	 * Checks if any tile will be flipped in the Down and Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkDownRight(int row, int col, char value) {
		return getScore(row, col, value, 1, 1);
	}

	/**
	 * Checks if any tile will be flipped in the Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkRight(int row, int col, char value) {
		return getScore(row, col, value, 1, 0);
	}

	/**
	 * Checks if any tile will be flipped in the Up and Right direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkUpRight(int row, int col, char value) {
		return getScore(row, col, value, 1, -1);
	}

	/**
	 * Checks if any tile will be flipped in the Up direction
	 * @param row : row location
	 * @param col : column location
	 * @param value : value of piece placed
	 * @return The score of a tile being place at that location
	 */
	private int checkUp(int row, int col, char value) {
		return getScore(row, col, value, 0, -1);
	}

	public String toString()
	{
		String toReturn = "(\t(";
		for(int i = 0; i < grid.length; ++i)
		{
			for(int j = 0; j < grid[i].length; ++j)
				toReturn += grid[i][j];
			toReturn += ")\t";
			if(i < 7)
				toReturn += "(";
		}
		toReturn += ")";
		return toReturn;
				
	}
	
	/**
	 * Sets the heuristic method to be used when computing a {@link Move} score
	 * @param heuristic : {@link Heuristic} method to use
	 */
	public void setHeuristic(Heuristic heuristic){
		this.heuristic = heuristic;
	}
	
	/**
	 * @return {@link Heuristic} method used to compute a score for a {@link Move}
	 */
	public Heuristic getHeuristic(){
		return heuristic;
	}
	
	
	
	private static int[][] gridWeights;
	private static void setGridWeights()
	{
		gridWeights = new int[8][8];
		for(int i = 0; i < gridWeights.length; ++i)
		{
			for(int j = 0; j < gridWeights[i].length; ++j)
			{
				if((j == 1 && (i <= 1 || i >= 6)) 
						|| (j == 6 && (i <= 1 || i >= 6)))
					gridWeights[i][j] = -1;
				else
					gridWeights[i][j] = 1;
			}
				
		}
		gridWeights[1][0] = -1;
		gridWeights[1][7] = -1;
		gridWeights[6][0] = -1;
		gridWeights[6][7] = -1;
//		System.out.println("GRID WEIGHTS: ");
//		for(int i = 0; i < gridWeights.length; ++i)
//		{
//			for(int j = 0; j < gridWeights[i].length; ++j)
//			{
//				System.out.print(gridWeights[i][j] + ", ");
//			}
//			System.out.println();
//		}
		
	}
	
	public static int getMoveWeight(int row, int col)
	{
		if(gridWeights == null)
			setGridWeights();
		
		return gridWeights[row][col];
	}
	
}

